
#include "dragonet.h"

#define PROC_INFO               "info"
#define PROC_VER                "version"
#define PROC_DEV                "dev"
#define PROC_RULES              "rules"
#define PROC_PLUGINS_INFO       "plugins_info"

struct proc_dir_entry *drg_proc_dir = NULL, *drg_proc_dev_dir = NULL;
struct proc_dir_entry *drg_proc_ver = NULL;
struct proc_dir_entry *drg_proc_info = NULL;
static struct file_operations drg_proc_info_fops;

typedef struct proc_info 
{
	int count;
} proc_info_t, *proc_info_p;

static struct proc_info info;

static int 
drg_proc_get_ver(char *buf, char **start, off_t offset,
	int len, int *unused, void *data)
{
	int rlen = 0;

	if (!data) {
		rlen =  sprintf(buf,      "Name:       %s\n", DRAGONET);
		rlen += sprintf(buf+rlen, "Ver:        %s\n", SVN_REV);
		rlen += sprintf(buf+rlen, "Count:      %d\n", info.count);
	}

	return rlen;
}

static void *
drg_proc_info_seq_start(struct seq_file *sf, loff_t *pos)
{
	struct proc_info *info = sf->private;
	unsigned int *i;

	/**
	 * avoid cat read second time
	 */
	if (*pos >= info->count) {
		return NULL;
	}

	/**
	 * store iterate info
	 */ 
	i = kmalloc(sizeof(unsigned int), GFP_ATOMIC);
	if (!i) {
		return ERR_PTR(-ENOMEM);
	}

	*i = *pos;
	return i;
}

static void *
drg_proc_info_seq_next(struct seq_file *sf, void *v, loff_t *pos)
{
	struct proc_info *info = sf->private;
	unsigned int *i = (unsigned int *)v;

	*i = ++(*pos);
	if (*pos >= info->count) {
		return NULL;
	}

	return i;
}

static void 
drg_proc_info_seq_stop(struct seq_file *sf, void *v)
{
	kfree(v);
	v = NULL;
}

static int 
drg_proc_info_seq_show(struct seq_file *sf, void *v)
{
	struct proc_info *info = sf->private;
	unsigned int *i = (unsigned int *)v;
	struct timespec now = CURRENT_TIME_SEC;

	switch (*i) {
		case 0: 
			seq_puts(sf, "index  count  time\n");
			seq_puts(sf, "======================================\n");
			break;

		default:
			seq_printf(sf, "%2u  %8u  %8ld",
					*i,
					info->count,
					(long)now.tv_sec);

			seq_putc(sf, '\n');
			break;
	}

	return 0;
}

static struct seq_operations drg_proc_info_seq_ops = {
	.start = drg_proc_info_seq_start,
	.next  = drg_proc_info_seq_next,
	.stop  = drg_proc_info_seq_stop,
	.show  = drg_proc_info_seq_show
};

static ssize_t 
drg_proc_info_write(struct file *file, const char __user *input, size_t size, loff_t *ofs)
{
	char buffer[32];
	struct proc_dir_entry *pde = PDE(file->f_dentry->d_inode);
	struct proc_info *info = pde->data;
	
	if (copy_from_user(buffer, input, 32))
		return -EFAULT;

	buffer[31] = '\0';
	info->count = (int)simple_strtol(buffer, NULL, 0);

	return size;
}

static int 
drg_proc_info_open(struct inode *inode, struct file *file)
{
	struct seq_file *sf;
	int ret;

	ret = seq_open(file, &drg_proc_info_seq_ops);
	if (ret < 0)
		return ret;

	sf = file->private_data;
	sf->private = PDE(inode)->data;

	return 0;       
}

static struct file_operations drg_proc_info_fops = {
	.owner = THIS_MODULE,
	.open = drg_proc_info_open,
	.read = seq_read,
	.write = drg_proc_info_write,
	.llseek = seq_lseek,
	.release = seq_release
};

int 
drg_proc_init(void)
{
	int ret = -1;
	info.count = 5;

	/* create /proc/net/dragonet/ dir */
	drg_proc_dir = proc_mkdir(DRAGONET, init_net.proc_net);
	if (!drg_proc_dir) 
		goto out;
	
	/* create /proc/net/dragonet/dev/ dir */
	drg_proc_dev_dir = proc_mkdir(PROC_DEV, drg_proc_dir);
	if (!drg_proc_dev_dir)
		goto out1;

	/* create /proc/net/dragonet/version file*/
	drg_proc_ver = create_proc_read_entry(PROC_VER, 0 /* read-only */,
			drg_proc_dir,
			drg_proc_get_ver, NULL);
	if (!drg_proc_ver)
		goto out2;

	drg_proc_info = create_proc_entry(PROC_INFO, S_IWUSR | S_IRUSR, drg_proc_dir);
	if (!drg_proc_info) 
		goto out3;
	
	drg_proc_info->proc_fops = &drg_proc_info_fops;
	drg_proc_info->data = &info;

	ret = 0;
	return ret;

out3:
	remove_proc_entry(PROC_VER, drg_proc_dir);
out2:
	remove_proc_entry(PROC_DEV, drg_proc_dir);
out1:
	remove_proc_entry(DRAGONET, init_net.proc_net);
out:
	DPRINTK(ALERT, "create proc error!\n");
	return ret;
}

void
drg_proc_exit(void)
{
	remove_proc_entry(PROC_INFO, drg_proc_dir);
	remove_proc_entry(PROC_VER, drg_proc_dir);
	remove_proc_entry(PROC_DEV, drg_proc_dir);
	remove_proc_entry(DRAGONET, init_net.proc_net);
}

